package com.lx.core;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.jar.JarEntry;

public class ScanPackage {
        public static Set<Class<?>> scan(Set<String> pages , Predicate<Class<?>> p , String...scans ) throws Exception {
            Set<Class<?>> beans = new HashSet<>();
            for (String page:scans){//多个包
                if (page == null || pages.contains(page)) continue;
                pages.add(page);
                LXLog.debug("当前扫描包名:"+page);
                Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(page.replace(".", "/"));//获取当前包的所有类信息
                while (dirs.hasMoreElements()){
                    URL url = dirs.nextElement();// 获取下一个元素
                    String protocol = url.getProtocol();// 得到协议的名称
                    LXLog.debug("当前文件保存类型:"+protocol);
                    if ("file".equals(protocol)) {// 如果是以文件的形式保存在服务器上
                        String filePath = URLDecoder.decode(url.getFile(), "UTF-8");// 获取包的物理路径
                        findAndAddClassesInPackageByFile(page,filePath,beans,p);
                    }else if ("jar".equals(protocol)) {//查询jar包里的类
                        findAndAddClassByjar(url,page.replace('.', '/'),page,beans,p);
                    }
                }
            }
            return beans;
        }
        private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes, Predicate<Class<?>> p) throws ClassNotFoundException {
            File dir = new File(packagePath);
            if (!dir.exists() || !dir.isDirectory()) return;
            for (File file : dir.listFiles()) {
                if (file.isDirectory()) {// 如果是目录 则继续扫描
                    findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes,p);
                } else if (file.getName().endsWith(".class")){//去掉.class
                    Class<?> c = Class.forName((packageName.charAt(0)=='.'?packageName.substring(1):packageName) + '.' + (file.getName().substring(0, file.getName().length() - 6)));
                    if (isBeanClass(c) && p.test(c)){
                        LXLog.debug("扫描到当前class:"+c);
                        classes.add(c);
                    }
                }
            }
        }
        private static void findAndAddClassByjar(URL url,String packageDirName,String packageName,Set<Class<?>> classes, Predicate<Class<?>> p) throws ClassNotFoundException, IOException {
            Enumeration<JarEntry> entries = ((JarURLConnection) url.openConnection()).getJarFile().entries();// 从此jar包 得到一个枚举类
            while (entries.hasMoreElements()) {// 同样的进行循环迭代
                JarEntry entry = entries.nextElement();// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                String name = entry.getName();
                if (name.charAt(0) == '/') {name = name.substring(1);}//如果是以/开头的 获取后面的字符串
                if (name.startsWith(packageDirName)) {// 如果前半部分和定义的包名相同
                    int idx = name.lastIndexOf('/');
                    if ((idx != -1)) {// 如果可以迭代下去 并且是一个包
                        packageName = name.substring(0, idx).replace('/', '.');// 获取包名 把"/"替换成"."
                        if (name.endsWith(".class") && !entry.isDirectory()) {// 如果是一个.class文件 而且不是目录
                            Class c = Class.forName(packageName + '.' + (name.substring(packageName.length() + 1, name.length() - 6)));//去掉后面的".class" 获取真正的类名
                            if (isBeanClass(c) && p.test(c)){
                                LXLog.debug("扫描到当前class:"+c);
                                classes.add(c);
                            }
                        }
                    }
                }
            }
        }
        //说明: 判断类是正常类
        /**{ ylx } 2021/9/11 22:58 */
        private static boolean isBeanClass(Class<?> cls){
            return !Modifier.isAbstract(cls.getModifiers()) && !cls.isEnum() && !cls.isInterface() && !cls.isAnnotation();
        }
    }
